#include "eeprom_driver.h"
#include <rtdevice.h>

static struct rt_i2c_bus_device *i2c_bus = RT_NULL;
static const char *i2c_bus_name = "i2c1";

/* EEPROM memory layout */
#define CONFIG_COPIES 3
#define CONFIG_SIZE (EEPROM_TOTAL_SIZE / CONFIG_COPIES)

static rt_err_t eeprom_i2c_write(uint16_t addr, uint8_t *data, uint16_t len)
{
    struct rt_i2c_msg msgs[1];
    uint8_t addr_buf[2];
    
    if (i2c_bus == RT_NULL) {
        return RT_ERR_IO;
    }
    
    /* Prepare address buffer */
    addr_buf[0] = (addr >> 8) & 0xFF;
    addr_buf[1] = addr & 0xFF;
    
    /* Prepare message */
    msgs[0].addr = EEPROM_DEVICE_ADDR;
    msgs[0].flags = RT_I2C_WR;
    msgs[0].buf = addr_buf;
    msgs[0].len = 2;
    
    /* If data is provided, combine address and data */
    if (data != RT_NULL && len > 0) {
        /* For write operations, we need to send address followed by data */
        uint8_t *write_buf = rt_malloc(2 + len);
        if (write_buf == RT_NULL) {
            return RT_ERR_MEM;
        }
        
        write_buf[0] = addr_buf[0];
        write_buf[1] = addr_buf[1];
        rt_memcpy(write_buf + 2, data, len);
        
        msgs[0].buf = write_buf;
        msgs[0].len = 2 + len;
        
        rt_err_t result = rt_i2c_transfer(i2c_bus, msgs, 1);
        rt_free(write_buf);
        
        return result;
    } else {
        return rt_i2c_transfer(i2c_bus, msgs, 1);
    }
}

static rt_err_t eeprom_i2c_read(uint16_t addr, uint8_t *data, uint16_t len)
{
    struct rt_i2c_msg msgs[2];
    uint8_t addr_buf[2];
    
    if (i2c_bus == RT_NULL || data == RT_NULL) {
        return RT_ERR_PARAM;
    }
    
    /* Prepare address buffer */
    addr_buf[0] = (addr >> 8) & 0xFF;
    addr_buf[1] = addr & 0xFF;
    
    /* First message: write address */
    msgs[0].addr = EEPROM_DEVICE_ADDR;
    msgs[0].flags = RT_I2C_WR;
    msgs[0].buf = addr_buf;
    msgs[0].len = 2;
    
    /* Second message: read data */
    msgs[1].addr = EEPROM_DEVICE_ADDR;
    msgs[1].flags = RT_I2C_RD;
    msgs[1].buf = data;
    msgs[1].len = len;
    
    return rt_i2c_transfer(i2c_bus, msgs, 2);
}

rt_err_t eeprom_init(void)
{
    /* Find I2C bus */
    i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(i2c_bus_name);
    if (i2c_bus == RT_NULL) {
        LOG_E("I2C bus %s not found\n", i2c_bus_name);
        return RT_ERR_NOT_FOUND;
    }
    
    LOG_I("EEPROM driver initialized on I2C bus %s\n", i2c_bus_name);
    return RT_OK;
}

rt_err_t eeprom_read(uint16_t addr, uint8_t *data, uint16_t len)
{
    rt_err_t result;
    uint16_t bytes_read = 0;
    
    if (data == RT_NULL) {
        return RT_ERR_PARAM;
    }
    
    if (addr + len > EEPROM_TOTAL_SIZE) {
        LOG_E("EEPROM read address out of range: %d + %d > %d\n", 
              addr, len, EEPROM_TOTAL_SIZE);
        return RT_ERR_PARAM;
    }
    
    /* Read data in chunks if crossing page boundary */
    while (bytes_read < len) {
        uint16_t chunk_addr = addr + bytes_read;
        uint16_t chunk_len = len - bytes_read;
        
        /* Don't cross page boundary in single read */
        uint16_t page_boundary = ((chunk_addr / EEPROM_PAGE_SIZE) + 1) * EEPROM_PAGE_SIZE;
        uint16_t max_chunk_len = page_boundary - chunk_addr;
        
        if (chunk_len > max_chunk_len) {
            chunk_len = max_chunk_len;
        }
        
        result = eeprom_i2c_read(chunk_addr, data + bytes_read, chunk_len);
        if (result != RT_OK) {
            LOG_E("EEPROM read failed at addr 0x%04X, len %d\n", chunk_addr, chunk_len);
            return result;
        }
        
        bytes_read += chunk_len;
    }
    
    return RT_OK;
}

rt_err_t eeprom_write(uint16_t addr, uint8_t *data, uint16_t len)
{
    rt_err_t result;
    uint16_t bytes_written = 0;
    
    if (data == RT_NULL) {
        return RT_ERR_PARAM;
    }
    
    if (addr + len > EEPROM_TOTAL_SIZE) {
        LOG_E("EEPROM write address out of range: %d + %d > %d\n", 
              addr, len, EEPROM_TOTAL_SIZE);
        return RT_ERR_PARAM;
    }
    
    /* Write data in page-sized chunks */
    while (bytes_written < len) {
        uint16_t chunk_addr = addr + bytes_written;
        uint16_t chunk_len = len - bytes_written;
        
        /* Don't cross page boundary in single write */
        uint16_t page_boundary = ((chunk_addr / EEPROM_PAGE_SIZE) + 1) * EEPROM_PAGE_SIZE;
        uint16_t max_chunk_len = page_boundary - chunk_addr;
        
        if (chunk_len > max_chunk_len) {
            chunk_len = max_chunk_len;
        }
        
        result = eeprom_i2c_write(chunk_addr, data + bytes_written, chunk_len);
        if (result != RT_OK) {
            LOG_E("EEPROM write failed at addr 0x%04X, len %d\n", chunk_addr, chunk_len);
            return result;
        }
        
        bytes_written += chunk_len;
        
        /* Wait for write to complete */
        rt_thread_mdelay(EEPROM_WRITE_DELAY);
    }
    
    return RT_OK;
}

rt_bool_t eeprom_verify_data(uint8_t *data1, uint8_t *data2, uint16_t len)
{
    if (data1 == RT_NULL || data2 == RT_NULL) {
        return RT_FALSE;
    }
    
    for (uint16_t i = 0; i < len; i++) {
        if (data1[i] != data2[i]) {
            return RT_FALSE;
        }
    }
    
    return RT_TRUE;
}

uint16_t eeprom_calculate_crc(uint8_t *data, uint16_t len)
{
    uint16_t crc = 0xFFFF;
    
    if (data == RT_NULL) {
        return crc;
    }
    
    for (uint16_t i = 0; i < len; i++) {
        crc ^= (uint16_t)data[i] << 8;
        for (uint8_t j = 0; j < 8; j++) {
            if (crc & 0x8000) {
                crc = (crc << 1) ^ 0x1021;
            } else {
                crc <<= 1;
            }
        }
    }
    
    return crc;
}

rt_err_t eeprom_save_config(void *config, uint16_t size)
{
    rt_err_t result;
    uint8_t *data_buffer;
    uint16_t crc;
    
    if (config == RT_NULL || size == 0) {
        return RT_ERR_PARAM;
    }
    
    if (size > CONFIG_SIZE - 2) { /* Reserve 2 bytes for CRC */
        LOG_E("Config size too large: %d > %d\n", size, CONFIG_SIZE - 2);
        return RT_ERR_PARAM;
    }
    
    /* Allocate buffer for config data + CRC */
    data_buffer = rt_malloc(size + 2);
    if (data_buffer == RT_NULL) {
        return RT_ERR_MEM;
    }
    
    /* Copy config data */
    rt_memcpy(data_buffer, config, size);
    
    /* Calculate and append CRC */
    crc = eeprom_calculate_crc(data_buffer, size);
    data_buffer[size] = (crc >> 8) & 0xFF;
    data_buffer[size + 1] = crc & 0xFF;
    
    /* Save three copies */
    for (uint8_t i = 0; i < CONFIG_COPIES; i++) {
        uint16_t base_addr = i * CONFIG_SIZE;
        
        result = eeprom_write(base_addr, data_buffer, size + 2);
        if (result != RT_OK) {
            LOG_E("Failed to write config copy %d\n", i);
            rt_free(data_buffer);
            return result;
        }
        
        LOG_I("Config copy %d saved to EEPROM at 0x%04X\n", i, base_addr);
    }
    
    rt_free(data_buffer);
    return RT_OK;
}

rt_err_t eeprom_load_config(void *config, uint16_t size)
{
    rt_err_t result;
    uint8_t copies[CONFIG_COPIES][CONFIG_SIZE];
    uint16_t crcs[CONFIG_COPIES];
    rt_bool_t valid[CONFIG_COPIES] = {RT_FALSE};
    uint8_t valid_count = 0;
    
    if (config == RT_NULL || size == 0) {
        return RT_ERR_PARAM;
    }
    
    if (size > CONFIG_SIZE - 2) {
        return RT_ERR_PARAM;
    }
    
    /* Read all three copies */
    for (uint8_t i = 0; i < CONFIG_COPIES; i++) {
        uint16_t base_addr = i * CONFIG_SIZE;
        
        result = eeprom_read(base_addr, copies[i], size + 2);
        if (result != RT_OK) {
            LOG_E("Failed to read config copy %d\n", i);
            continue;
        }
        
        /* Verify CRC */
        crcs[i] = (copies[i][size] << 8) | copies[i][size + 1];
        uint16_t calc_crc = eeprom_calculate_crc(copies[i], size);
        
        if (crcs[i] == calc_crc) {
            valid[i] = RT_TRUE;
            valid_count++;
            LOG_I("Config copy %d CRC valid: 0x%04X\n", i, crcs[i]);
        } else {
            LOG_W("Config copy %d CRC invalid: stored=0x%04X, calc=0x%04X\n", 
                  i, crcs[i], calc_crc);
        }
    }
    
    if (valid_count == 0) {
        LOG_E("No valid config copies found\n");
        return RT_ERR_CHECKSUM;
    }
    
    /* Find two matching copies */
    for (uint8_t i = 0; i < CONFIG_COPIES; i++) {
        if (valid[i]) {
            for (uint8_t j = i + 1; j < CONFIG_COPIES; j++) {
                if (valid[j] && eeprom_verify_data(copies[i], copies[j], size)) {
                    /* Two copies match, use this config */
                    rt_memcpy(config, copies[i], size);
                    LOG_I("Using config from copies %d and %d\n", i, j);
                    return RT_OK;
                }
            }
        }
    }
    
    /* No two copies match, use first valid copy */
    for (uint8_t i = 0; i < CONFIG_COPIES; i++) {
        if (valid[i]) {
            rt_memcpy(config, copies[i], size);
            LOG_W("Using config from copy %d (no matching pair)\n", i);
            return RT_OK;
        }
    }
    
    return RT_ERR_CHECKSUM;
}